use crate::core::db_base::get_conn;
use anyhow::{Result, anyhow};
use sqlx::Row;
use sqlx::mysql::MySqlRow;
use time::{OffsetDateTime, PrimitiveDateTime};

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Passwd {
    pub(crate) id: u32,
    pub(crate) create_time: time::PrimitiveDateTime,
    pub(crate) user_id: u32,
    pub(crate) password: String,
    pub(crate) flag: TeacherOrStudent,
}

#[derive(Debug, serde_repr::Deserialize_repr, serde_repr::Serialize_repr, Clone, Copy)]
#[repr(u8)]
pub enum TeacherOrStudent {
    Teacher,
    Student,
}
impl TryFrom<u8> for TeacherOrStudent {
    type Error = anyhow::Error;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            1 => Ok(Self::Teacher),
            2 => Ok(Self::Student),
            _ => Err(anyhow::anyhow!(
                "Unknown value: {} for TeacherOrStudent",
                value
            )),
        }
    }
}
impl Into<u8> for TeacherOrStudent {
    fn into(self) -> u8 {
        match self {
            TeacherOrStudent::Teacher => 1,
            TeacherOrStudent::Student => 2,
        }
    }
}

impl Passwd {
    pub fn new(user_id: u32, passwd: String, flag: TeacherOrStudent) -> Self {
        let local_now = OffsetDateTime::now_local().expect("get local offset failed!");
        let create_time = PrimitiveDateTime::new(local_now.date(), local_now.time());
        Self {
            id: 0u32,
            create_time,
            user_id,
            password: passwd,
            flag,
        }
    }
    pub async fn get_passwd_by_uid(uid: u32, flag: TeacherOrStudent) -> Result<Option<Self>> {
        let conn = get_conn().await?;
        let t = sqlx::query(
            "SELECT id,create_time,user_id,password,flag FROM passwd WHERE user_id=? and flag=?;",
        )
        .bind(uid)
        .bind(Into::<u8>::into(flag))
        .map(Self::map_row)
        .fetch_optional(&conn)
        .await?;
        Ok(t)
    }
    fn map_row(row: MySqlRow) -> Self {
        Self {
            id: row.get("id"),
            create_time: row.get("create_time"),
            user_id: row.get("user_id"),
            password: row.get("password"),
            flag: row.get::<u8, &str>("flag").try_into().unwrap(),
        }
    }

    pub async fn add_passwd(&self) -> Result<()> {
        let conn = get_conn().await?;
        let sql = r#"insert into passwd (create_time,user_id,password,flag)values(NOW(),?,?,?);"#;
        let effcont = sqlx::query(sql)
            .bind(self.user_id)
            .bind(&self.password)
            .bind(Into::<u8>::into(self.flag))
            .execute(&conn)
            .await?;
        if effcont.rows_affected() > 0 {
            Ok(())
        } else {
            Err(anyhow!("成功执行，但未产生任何影响"))
        }
    }

    pub fn is_new(&self) -> bool {
        self.id == 0
    }
}
